package com.tnsw.noxnox.downloader;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;

import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.http.client.HttpClient;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.tnsw.coreutils.AsyncManager;
import com.tnsw.coreutils.http.THttpBase;
import com.tnsw.coreutils.http.THttpGet;
import com.tnsw.coreutils.http.THttpListener;
import com.tnsw.coreutils.text.TextUtils;
import com.tnsw.noxnox.db.DBManager;
import com.tnsw.noxnox.db.tables.DownloadProgress;

import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;

public class YoutubeDL extends DLBase {

	public static String[] YOUTUBE_PARAMS = {"factor", "algorithm", "burst", "newshard", "ratebypass", "gcr", "ipbits", "id", "sver", "ip", "cp", "ms", "fexp", "mv", "upn", "mt", "sparams", "source", "key", "expire", "itag", "signature"};
	private String tag = "YoutubeDL";
	
	private THttpGet httpFirst;
	private THttpGet httpContent;
	private THttpGet httpImages;  // this is for XML
	private THttpGet httpSingleImage; // this is for image
	
	
	private AsyncManager asyncManager;
	
	private Hashtable<Integer, YoutubeAttr> hashAttrs;  // hash of itag and YoutubeAttr
	
	private int lengthSeconds;
	private String videoId = "";
	
	public YoutubeDL(Context context, String firstUrl) {
		super(context, firstUrl);
		super.dp.video_sites = 1;
		this.asyncManager = AsyncManager.getInstance();
		
		Uri uri = Uri.parse(firstUrl);
		this.videoId = uri.getQueryParameter("v");
		
		String host = THttpBase.getHost2(firstUrl);
		Log.d(tag, "host=" + host);
		
		this.httpFirst = new THttpGet(context, firstUrl);
		this.httpFirst.setSessionName("metadata");
		this.httpFirst.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
		this.httpFirst.addHeader("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.3");
		this.httpFirst.addHeader("Accept-Encoding", "gzip,deflate,sdch");
		this.httpFirst.addHeader("Connection", "keep-alive");
		this.httpFirst.addHeader("Accept-Language", "th,en-US;q=0.8,en;q=0.6");
		this.httpFirst.addHeader("Host", THttpBase.getHost2(firstUrl));
		this.httpFirst.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4");
		
		this.httpFirst.setListener(this);

	}
		
	@Override
	public void doit() {
		super.doit();
		this.isMetaDataOK = false;
		this.httpFirst.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "0");
		this.setTextStatus("Downloading meta data..");
	}
	
	private void initialHttpImages() {
		String url = "https://gdata.youtube.com/feeds/api/videos/" + this.videoId;
		this.httpImages = new THttpGet(context, url);
		this.httpImages.setSessionName("images");
		this.httpImages.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
		this.httpImages.addHeader("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.3");
		this.httpImages.addHeader("Accept-Encoding", "gzip,deflate,sdch");
		this.httpImages.addHeader("Connection", "keep-alive");
		this.httpImages.addHeader("Accept-Language", "th,en-US;q=0.8,en;q=0.6");
		this.httpImages.addHeader("Host", THttpBase.getHost(url));
		this.httpImages.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4");
		
		this.httpImages.setListener(this);
		this.httpImages.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "0");
	}
	
	private void initialHttpSingleImage(String url) {
		Uri uri = Uri.parse(url);
		this.httpSingleImage = new THttpGet(context, url);
		
		if (uri.getLastPathSegment() != null) {
			this.asyncManager.appendNetworkLog("lastPathSegment=" + uri.getLastPathSegment());
			String extension = uri.getLastPathSegment().split("\\.")[1];
			this.httpSingleImage.setPredefinedExtension(extension);
		}
		
		
		this.httpSingleImage.setSessionName("single_image");
		this.httpSingleImage.setBinaryDirection(THttpBase.BINARY_TO_FILE);
		this.httpSingleImage.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
		this.httpSingleImage.addHeader("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.3");
		this.httpSingleImage.addHeader("Accept-Encoding", "gzip,deflate,sdch");
		this.httpSingleImage.addHeader("Connection", "keep-alive");
		this.httpSingleImage.addHeader("Accept-Language", "th,en-US;q=0.8,en;q=0.6");
		this.httpSingleImage.addHeader("Host", THttpBase.getHost(url));
		this.httpSingleImage.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4");
		
		this.httpSingleImage.setListener(this);
		this.httpSingleImage.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "0");		
	}

	@Override
	public void onProgressUpdate(THttpBase sender, Integer[] values) {
		super.onProgressUpdate(sender, values);
		
		if (super.dlInterface != null) {
			if (sender.getSessionName().equals("binary")) {
				this.setMax(sender.getContentLength());
				this.setCurrentPosition((int) sender.getCurrentPosition());
				
				float percentage = (((float) sender.getCurrentPosition()/ (float) sender.getContentLength())*(float) 100);
				this.setTextStatus(String.valueOf(percentage) + "%");
				
			}
			
			this.dlInterface.onProgressUI(this);
		}
		
	}

	@Override
	public void onPostExecute(THttpBase sender, Long result) {
		/*
		if (this.isMetaDataOK == false) {
			// Finished metadata stream and cannot find Metadata
			if (this.dlInterface != null) {
				this.dlInterface.onError(this, false, "Cannot find metadata", null);
				return;
			}
		}
		*/
		super.onPostExecute(sender, result);
		
		if (sender.getSessionName().equals("binary")) {
			if (sender.getHttpResponse().getStatusLine().getStatusCode() == 200) {
				if (sender.getCurrentSB() != null) {
					this.asyncManager.appendNetworkLog("Expected binary but found text");
					this.asyncManager.appendNetworkLog(sender.getCurrentSB().toString());
				}
				
				if (this.dlInterface != null) {
					this.setTextStatus("Completed");
					this.dlInterface.onCompletedUI(this, 0);
				}	
			}
			
		} else if (sender.getSessionName().equals("images")) {
			if (sender.getHttpResponse().getStatusLine().getStatusCode() == 200) {
				String xmlStr = sender.getCurrentSB().toString();
				this.asyncManager.appendNetworkLog("images=" + xmlStr);
				
				this.updateThumbnails(xmlStr);
			}
			
		} else if (sender.getSessionName().equals("single_image")) {
			if (sender.getHttpResponse().getStatusLine().getStatusCode() == 200) {
				String filename = sender.getCurrentBinaryfileName();
				this.dp.thumbnail_filename = filename;
				DBManager dbm = DBManager.getInstance(this.context);
				dbm.updateDownloadProgress(this.dp, true);
			}
		}
		
	}
	
	private void updateThumbnails(String xmlStr) {
		Document dom;
		
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setNamespaceAware(true);
		dbf.setIgnoringElementContentWhitespace(true);
		XPath xpath = XPathFactory.newInstance().newXPath();
		
		xpath.setNamespaceContext(new SimpleNameSpaceContext());
				
		try {
			javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
			
			InputStream ins = new ByteArrayInputStream(xmlStr.getBytes());
			
			Log.d(tag, "Started Parsing..");
			dom = db.parse(ins);
			dom.getDocumentElement().normalize();
			
			
			// Node actorsNode = (Node) this.xpath.evaluate("Actors", this.dom, XPathConstants.NODE);
			NodeList thumbnailNL = (NodeList) xpath.evaluate("//media:thumbnail", dom, XPathConstants.NODESET);
			if (thumbnailNL != null) {
				this.asyncManager.appendNetworkLog("thumbnailNL != null");
				if (thumbnailNL.getLength() > 0) {
					int i;
					for (i = 0; i < thumbnailNL.getLength(); i++) {
						Node thumbnailNode = thumbnailNL.item(i);
						String url = "";
						String time = "";
						int width = 0;
						int height = 0;
						
						if (thumbnailNode.getAttributes().getNamedItem("url") != null) {
							url = thumbnailNode.getAttributes().getNamedItem("url").getNodeValue();
						}
						
						if (thumbnailNode.getAttributes().getNamedItem("time") != null) {
							time = thumbnailNode.getAttributes().getNamedItem("time").getNodeValue();
						}
						
						if (thumbnailNode.getAttributes().getNamedItem("width") != null) {
							width = Integer.valueOf(thumbnailNode.getAttributes().getNamedItem("width").getNodeValue());
						}
						
						if (thumbnailNode.getAttributes().getNamedItem("height") != null) {
							height = Integer.valueOf(thumbnailNode.getAttributes().getNamedItem("height").getNodeValue());
						}
						
						this.asyncManager.appendNetworkLog("url=" + url + ", width=" + width + ", height=" + height + ", time=" + time);
						
						if (i == 0) {
							this.initialHttpSingleImage(url);
						}
					}
				}
				
				
				
			} else {
				this.asyncManager.appendNetworkLog("thumbnailNL == null");
			}
			
		} catch (Exception e) {
			Log.e(tag, "updateThuimbnails() - " + e.toString(), e);
		}
		
		
	}
	
	private void createHashResolutions(String fmtList) {
		this.hashAttrs= new Hashtable<Integer, YoutubeAttr>();
		String fmtLists[] = fmtList.split(",");
		
		for (String s : fmtLists) {
			String[] ss = s.split("/");
			String[] resolutions = ss[1].split("x");
			int width = Integer.valueOf(resolutions[0]);
			int height = Integer.valueOf(resolutions[1]);
			int itag = Integer.valueOf(ss[0]);
			
			YoutubeAttr attr = new YoutubeAttr();
			attr.setWidth(width);
			attr.setHeight(height);
			
			super.hashStdAttrs.put("width", String.valueOf(width));
			super.hashStdAttrs.put("height", String.valueOf(height));
			
			this.hashAttrs.put(itag, attr);
			
			Log.d(tag, "added hashResolutions[itag=" + itag + ", width=" + width + ", height=" + height + "]");
		}
	}
	
	public String getFmtUrl(String line) {
		
		if (line.contains("playerConfig") == true) {
			
			Log.d(tag, "Find binary url from this line: " + line);
			
			int idx = line.indexOf("=");
			String jsonStr = line.substring(idx+1, line.length()-1);
			
			try {
				this.asyncManager.appendNetworkLog("jsonstr: " + jsonStr);
				JSONObject jsObj = new JSONObject(jsonStr);
				JSONObject jsArgs = jsObj.getJSONObject("args");
				String urlEncodedFmtStreamMap = jsArgs.getString("url_encoded_fmt_stream_map");
				
				if (jsArgs.has("length_seconds") == true) {
					this.lengthSeconds = jsArgs.getInt("length_seconds");
					this.dp.duration_sec = this.lengthSeconds;
				}
				
				if (jsArgs.has("title") == true) {
					String title = jsArgs.getString("title");
					this.setTitle(title);
					this.dp.title = title;
					Log.d(tag, "Found title=" + title);
				}
				
				if (jsArgs.has("fmt_list") == true) {
					String fmtList = jsArgs.getString("fmt_list");
					Log.d(tag, "fmt_list=" + fmtList);
					
					this.createHashResolutions(fmtList);
				}
				
				this.isMetaDataOK = true;
				return(urlEncodedFmtStreamMap);
				
			} catch (Exception e) {
				Log.e(tag, "getFmtUrl(type=1) " + e.toString(), e);
			}
			
		} else if (line.contains("var ytplayer = ytplayer") == true) {
			// Log.d(tag, "Found2: " + line);
			
			this.asyncManager.appendNetworkLog("Found 2: " + line);
			
			int idx1 = line.indexOf("=");
			int idx2 = line.indexOf("=", idx1+1);
			String jsonStr = line.substring(idx2+1, line.length()-1);
			
			this.asyncManager.appendNetworkLog("jsonStr2:" + jsonStr);
			try {
				JSONObject jsObj = new JSONObject(jsonStr);
				JSONObject jsArgs = jsObj.getJSONObject("args");
				String urlEncodedFmtStreamMap = jsArgs.getString("url_encoded_fmt_stream_map");
				
				if (jsArgs.has("length_seconds") == true) {
					this.lengthSeconds = jsArgs.getInt("length_seconds");
					this.dp.duration_sec = this.lengthSeconds;
				}
				
				if (jsArgs.has("title") == true) {
					String title = jsArgs.getString("title");
					this.setTitle(title);
					this.dp.title = title;
					Log.d(tag, "Found title=" + title);
				}
				
				if (jsArgs.has("fmt_list") == true) {
					String fmtList = jsArgs.getString("fmt_list");
					Log.d(tag, "fmt_list=" + fmtList);
					
					this.createHashResolutions(fmtList);
				}
				
				this.isMetaDataOK = true;
				return(urlEncodedFmtStreamMap);	
			} catch (Exception e) {
				Log.e(tag, "getFmtUrl(type=2) " + e.toString(), e);
			}
			
		}
		
		
		return("");
	}

	@Override
	public void onNewLineFetchBG(THttpBase sender, String line) {
		String fmtUrl = this.getFmtUrl(line);
		if ( (this.isMetaDataOK == true) && (sender.getSessionName().equals("metadata")) ) {
			sender.stop();
			
			Uri uri;
			YoutubeAttr attr = null;
			String[] urls = this.getUrls(fmtUrl);
			if (urls != null) {
				
				for (String url : urls) {
					uri = Uri.parse(url);
					if (uri.getQueryParameter("itag") != null) {
						int itag = Integer.valueOf(uri.getQueryParameter("itag"));
						attr = this.hashAttrs.get(itag);
						if (attr != null) {
							this.asyncManager.appendNetworkLog(attr.toString() + ":" + url);
							
							/*
							this.hashStdAttrs.put("codec", attr.getCodec());
							this.hashStdAttrs.put("content-type", attr.getContentType());
							this.hashStdAttrs.put("width", String.valueOf(attr.getWidth()));
							this.hashStdAttrs.put("height", String.valueOf(attr.getHeight()));
							*/
						}
					
						
					}
				}
			
				if (urls.length > 0) {
					this.initialHttpImages();
					
					String binaryUrl = urls[2];
					Uri binaryUri = Uri.parse(binaryUrl);
					YoutubeAttr selectedAttr = this.hashAttrs.get(Integer.valueOf(binaryUri.getQueryParameter("itag")));
					
					this.hashStdAttrs.put("codec", selectedAttr.getCodec());
					this.hashStdAttrs.put("content-type", selectedAttr.getContentType());
					this.hashStdAttrs.put("width", String.valueOf(selectedAttr.getWidth()));
					this.hashStdAttrs.put("height", String.valueOf(selectedAttr.getHeight()));
					
					HttpClient h = new DefaultHttpClient();
					this.httpContent = new THttpGet(this.context, binaryUrl);
					this.httpContent.setHttpClient(h);

					this.httpContent.getHttpClient().getParams().setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true);
					if (attr != null) {
						this.httpContent.setPredefinedExtension(selectedAttr.getExtension());
					}
						
					this.httpContent.setSessionName("binary");
					this.httpContent.setBinaryDirection(THttpBase.BINARY_TO_FILE);
					this.httpContent.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
					this.httpContent.addHeader("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.3");
					this.httpContent.addHeader("Accept-Encoding", "gzip,deflate,sdch");
					this.httpContent.addHeader("Connection", "keep-alive");
					this.httpContent.addHeader("Accept-Language", "th,en-US;q=0.8,en;q=0.6");
					this.httpContent.addHeader("Host", THttpBase.getHost(binaryUrl));
					this.httpContent.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4");
					this.httpContent.setListener(this);
						
					//this.httpContent.execute("0");
					this.httpContent.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "0");
					Log.d(tag, "Started download binary..");
						
				} else {
					Log.w(tag, "cannot find any binary url, urls.length==0");
				}
												
			} else {
				Log.e(tag, "cannot find any binary url, urls == null");
			}
		}

	}
		
	private String[] getUrls(String urlEncode) {
		ArrayList<String> a = new ArrayList<String>();
		
		String keyword = urlEncode.split("=")[0];
		Log.d(tag, "getUrls, using keyword={" + keyword + "}");
		Object[] obj = TextUtils.getValueFrom(urlEncode, 0, keyword, "," + keyword);
		
		String draftUrl = (String) obj[0];
		int lastIdx = (Integer) obj[1];
		
		while (draftUrl.equals("") == false) {
			int urlIdx = draftUrl.indexOf("&url");
			String prefix = "";
			
			if ( (urlIdx == -1) && (keyword.equals("url")) ) {
				urlIdx = 0;
			}
			
			//Log.d(tag, "urlIdx=" + urlIdx);
			prefix = draftUrl.substring(0, urlIdx);
			//Log.d(tag, "prefix=" + prefix);
			
			Object[] obj2 = TextUtils.getValueFrom(draftUrl, 0, "url", "asdf1234cvbn");
			String postfix = (String) obj2[0];
			//Log.d(tag, "postfix=" + postfix);
			
			String realUrl = postfix.substring("url=".length()) + "&" + prefix;
			//Log.d(tag, "before_realUrl=" + realUrl);
			realUrl = realUrl.replace("&sig", "&signature");
			
			realUrl = realUrl.replace("%25", "%");
			realUrl = URLDecoder.decode(realUrl);
			
			Uri uri = Uri.parse(realUrl);
			this.asyncManager.appendNetworkLog("realUrl: " + realUrl);
			realUrl = this.createSingleUrl(realUrl);
			
			//Log.d(tag, "realUrl=" + realUrl);
			//asyncManager.appendNetworkLog("(type=" + uri.getQueryParameter("type") + ", " + uri.getQueryParameter("quality") + "), realUrl=" + realUrl);
			a.add(realUrl);
			
			obj = null;
			obj = TextUtils.getValueFrom(urlEncode, lastIdx, keyword, "," + keyword);
			draftUrl = (String) obj[0];
			lastIdx = (Integer) obj[1];
		}
				
		//Log.d(tag, "getUrls, first_draftUrl=" + draftUrl + ", first_lastIdx=" + lastIdx);		
		
		String[] s = a.toArray(new String[a.size()]);
		return(s);
	}	
	
	private String createSingleUrl(String xUrl) {
		Uri uri = Uri.parse(xUrl);
		String prefix = uri.getScheme() + "://" + uri.getHost() + uri.getPath();
		String url = prefix;
		
		int i = 0;
		for (String param : YOUTUBE_PARAMS) {
			
			if (uri.getQueryParameter(param) != null) {
				if (i > 0) {
					url = url + "&";
				} else if (i == 0) {
					url = url + "?";
				}
				
				url = url + param + "=" + uri.getQueryParameter(param);
				
				i += 1;	
			} else {
				Log.w(tag, "createSingleUrl() -- cannot find key='" + param + "' in YOUTUBE_PARAMS");
			}
			
		}
		
		if (uri.getQueryParameter("itag") != null) {
			int itag = Integer.valueOf(uri.getQueryParameter("itag"));
			YoutubeAttr attr = this.hashAttrs.get(itag);
			if (attr != null) {
				String type = uri.getQueryParameter("type");
				attr.setContentType(type);
				
				String quality = uri.getQueryParameter("quality");
				attr.setQuality(quality);
			} else {
				Log.w(tag, "YoutubeDL.createSingleURL - failed to update attribute, cannot find itag from url: " + url);
			}
		}
		
		return(url);
	}

	@Override
	public void onReceiveResponseBG(THttpBase sender) {
		if (sender.getSessionName().equals("binary")) {
			String url = sender.getUrl();
			Uri uri = Uri.parse(url);
			Log.d(tag, "downloading this url=" + url);
			
			/*
			YoutubeAttr attr = this.hashAttrs.get(Integer.valueOf(uri.getQueryParameter("itag")));
			
			this.hashStdAttrs.put("codec", attr.getCodec());
			this.hashStdAttrs.put("content-type", attr.getContentType());
			this.hashStdAttrs.put("width", String.valueOf(attr.getWidth()));
			this.hashStdAttrs.put("height", String.valueOf(attr.getHeight()));
			*/			
		}

		super.onReceiveResponseBG(sender);
		
		/*
		DownloadProgress dp = new DownloadProgress();
		if (sender.getHttpResponse() != null) {
			if (sender.getHttpResponse().getStatusLine().getStatusCode() == 200) {
				dp.content_length = (int) sender.getContentLength();
				dp.current_position = 0;
				dp.status = 1;
				dp.tag_key = "tagkey";
				dp.tag_value = "tagvalue";
				dp.url = this.firstUrl;
				dp.video_sites = 1;
				
				DBManager dbm = DBManager.getInstance(this.context);
				dbm.insertDownloadProgress(dp);
			}
		}
		*/

	}

}
